/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <gtest/gtest.h>

#include <internal/chip/chip_ads78/ads78.h>
#include <mc/dict.h>
#include <mc/exception.h>

#include <cstring>
#include <deque>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

namespace {

class ads78_test_bus : public dev::bus_base {
public:
    ads78_test_bus() = default;
    ~ads78_test_bus() override = default;

    uint32_t read(ACCESS_DATA_S& input) override {
        ++m_read_call_count;
        if (m_throw_on_read) {
            throw std::runtime_error(m_exception_message);
        }
        m_last_access = input;
        if (!m_read_payloads.empty()) {
            input.out_buffer = m_read_payloads.front();
            m_read_payloads.pop_front();
        } else {
            input.out_buffer.clear();
        }
        return m_read_result;
    }

    uint32_t write(ACCESS_DATA_S& input) override {
        ++m_write_call_count;
        m_last_access = input;
        return HAS_OK;
    }

    uint8_t get_id() override {
        return 0;
    }

    void bus_lock() override {
    }

    void bus_unlock() override {
    }

    void enqueue_read_payload(std::vector<uint8_t> payload) {
        m_read_payloads.emplace_back(std::move(payload));
    }

    void set_read_result(int32_t result) {
        m_read_result = result;
    }

    void set_throw_on_read(bool enabled, std::string message = "mock read exception") {
        m_throw_on_read      = enabled;
        m_exception_message  = std::move(message);
    }

    int read_call_count() const {
        return m_read_call_count;
    }

    const ACCESS_DATA_S& last_access() const {
        return m_last_access;
    }

private:
    std::deque<std::vector<uint8_t>> m_read_payloads;
    int32_t                          m_read_result       = HAS_OK;
    bool                             m_throw_on_read     = false;
    std::string                      m_exception_message = "mock read exception";
    int                              m_read_call_count   = 0;
    int                              m_write_call_count  = 0;
    ACCESS_DATA_S                    m_last_access       = {};
};

class chip_ads78_test : public ::testing::Test {
protected:
    void SetUp() override {
        mc::mutable_dict csr;
        csr["Address"]     = static_cast<uint32_t>(0x50);
        csr["OffsetWidth"] = static_cast<uint8_t>(1);
        csr["AddrWidth"]   = static_cast<uint8_t>(1);
        ASSERT_TRUE(m_chip.init(csr));
    }

    std::shared_ptr<ads78_test_bus> bind_test_bus() {
        auto bus = std::make_shared<ads78_test_bus>();
        m_chip.set_left_bus(bus);
        m_chip.set_host_bus(bus);
        return bus;
    }

    dev::chip_ads78 m_chip;
};

TEST_F(chip_ads78_test, bit_read_converts_adc_value_to_millivolts) {
    auto bus = bind_test_bus();

    constexpr uint16_t raw_adc_value = 0x0800; // 2048
    bus->enqueue_read_payload(
        {static_cast<uint8_t>((raw_adc_value >> 8) & 0x0F), static_cast<uint8_t>(raw_adc_value & 0xFF)});

    std::vector<uint8_t> in_buffer;
    std::vector<uint8_t> out_buffer(2, 0);
    std::string          error;

    EXPECT_EQ(m_chip.bit_read(0, 2, 0, in_buffer, out_buffer, error), HAS_OK);
    EXPECT_TRUE(error.empty());
    EXPECT_EQ(bus->read_call_count(), 1);

    // Channel 0 -> register 8, expect offset ((8 << 4) | 0x4)
    EXPECT_EQ(bus->last_access().offset, 0x84U);
    EXPECT_EQ(bus->last_access().length, 2U);

    constexpr uint16_t expected_mv = static_cast<uint16_t>((static_cast<uint32_t>(raw_adc_value) * 2500U) / 4096U);
    EXPECT_EQ(out_buffer[0], static_cast<uint8_t>((expected_mv >> 8) & 0xFF));
    EXPECT_EQ(out_buffer[1], static_cast<uint8_t>(expected_mv & 0xFF));
}

TEST_F(chip_ads78_test, bit_read_throws_when_bus_is_missing) {
    dev::chip_ads78 chip_without_bus;
    mc::mutable_dict csr;
    ASSERT_TRUE(chip_without_bus.init(csr));

    std::vector<uint8_t> in_buffer;
    std::vector<uint8_t> out_buffer(2, 0);
    std::string          error;

    EXPECT_THROW(chip_without_bus.bit_read(0, 2, 0, in_buffer, out_buffer, error), mc::invalid_op_exception);
}

TEST_F(chip_ads78_test, bit_read_returns_error_for_invalid_channel) {
    auto bus = bind_test_bus();
    std::vector<uint8_t> in_buffer;
    std::vector<uint8_t> out_buffer(2, 0);
    std::string          error;

    EXPECT_EQ(m_chip.bit_read(8, 2, 0, in_buffer, out_buffer, error), HAS_ERR);
    EXPECT_NE(error.find("invalid channel"), std::string::npos);
    EXPECT_EQ(bus->read_call_count(), 0);
}

TEST_F(chip_ads78_test, bit_read_returns_error_when_bus_read_fails) {
    auto bus = bind_test_bus();
    bus->set_read_result(HAS_ERR);
    bus->enqueue_read_payload({0x00, 0x00});

    std::vector<uint8_t> in_buffer;
    std::vector<uint8_t> out_buffer(2, 0);
    std::string          error;

    EXPECT_EQ(m_chip.bit_read(1, 2, 0, in_buffer, out_buffer, error), HAS_ERR);
    EXPECT_NE(error.find("read operation failed"), std::string::npos);
    EXPECT_EQ(bus->read_call_count(), 1);
}

TEST_F(chip_ads78_test, bit_read_returns_error_when_data_is_insufficient) {
    auto bus = bind_test_bus();
    bus->enqueue_read_payload({0x08});

    std::vector<uint8_t> in_buffer;
    std::vector<uint8_t> out_buffer(2, 0);
    std::string          error;

    EXPECT_EQ(m_chip.bit_read(2, 2, 0, in_buffer, out_buffer, error), HAS_ERR);
    EXPECT_NE(error.find("insufficient data"), std::string::npos);
}

TEST_F(chip_ads78_test, bit_read_handles_bus_exception) {
    auto bus = bind_test_bus();
    bus->set_throw_on_read(true, "forced failure");

    std::vector<uint8_t> in_buffer;
    std::vector<uint8_t> out_buffer(2, 0);
    std::string          error;

    EXPECT_EQ(m_chip.bit_read(3, 2, 0, in_buffer, out_buffer, error), HAS_ERR);
}

} // namespace

